<template>
  <div class="page">
    <!-- 页面头部 -->
    <div class="page-header">
      <div class="page-title-block">
        <div class="page-title">
          可利用节点全球分布
          <span class="page-badge">国家级热度图</span>
        </div>
        <div class="page-subtitle">
          按国家聚合扫描到的可利用 DNS 节点数量，高密度区域颜色更深；下方表格展示城市、国家、ASN、
          运营商、转发器 IP 与访问方式等信息。
        </div>
      </div>
    </div>

    <section class="card map-card">
      <!-- 卡片头部 -->
      <div class="card-header">
        <div>
          <div class="card-title">
            <div class="card-title-icon">🌐</div>
            可利用节点分布地图
          </div>
          <div class="card-subtitle">
            基于数据库中的可利用 DNS 节点与地理位置，按国家聚合形成热度图。
          </div>
        </div>
      </div>

      <!-- 地图区域 -->
      <div ref="mapContainer" class="map-container">
        <div v-if="mapLoading" class="map-loading-overlay">地图渲染中...</div>

        <!-- 图例 -->
        <div class="map-legend">
          <div class="map-legend-title">国家热度</div>
          <!-- 0 -->
          <div class="map-legend-item">
            <span
              class="map-legend-color"
              style="background:#f5f5ff; border: 1px solid #eee;"
            ></span>
            <span>0</span>
          </div>
          <!-- 4档颜色 -->
          <div class="map-legend-item">
            <span class="map-legend-color" style="background:#c9d4ff;"></span>
            <span>{{ legendLabels[0] }}</span>
          </div>
          <div class="map-legend-item">
            <span class="map-legend-color" style="background:#9fb3ff;"></span>
            <span>{{ legendLabels[1] }}</span>
          </div>
          <div class="map-legend-item">
            <span class="map-legend-color" style="background:#6c8bff;"></span>
            <span>{{ legendLabels[2] }}</span>
          </div>
          <div class="map-legend-item">
            <span class="map-legend-color" style="background:#3d5afe;"></span>
            <span>{{ legendLabels[3] }}</span>
          </div>
        </div>
      </div>

      <!-- 表格标题 -->
      <div class="table-section-title">可利用节点信息详情</div>
      <div class="table-section-subtitle">
        展示可利用 DNS 对应的城市、国家、ASN、运营商、转发器 IP、访问方式与 IP 前缀信息，支持分页加载。
      </div>

      <!-- 胶囊切换与工具栏 -->
      <div class="table-toolbar">
        <div class="toolbar-left">
          <div class="capsule-switch">
            <div
              class="capsule-item"
              :class="{ active: currentNodeType === 'forwarder' }"
              @click="switchNodeType('forwarder')"
            >
              DNS 转发器
            </div>
            <div
              class="capsule-item"
              :class="{ active: currentNodeType === 'resolver' }"
              @click="switchNodeType('resolver')"
            >
              DNS 解析器
            </div>
          </div>
        </div>

        <div class="toolbar-right">
          <span>共 {{ totalCount }} 条记录</span>
          <span>
            每页
            <select v-model.number="pageSize" class="page-size-select">
              <option :value="10">10</option>
              <option :value="20">20</option>
              <option :value="50">50</option>
            </select>
            条
          </span>
        </div>
      </div>

      <!-- 表格主体 -->
      <div class="table-wrapper">
        <div v-if="tableLoading" class="table-loading-overlay">加载中...</div>

        <table>
          <thead>
            <tr>
              <th style="width:13%">节点 IP</th>
              <th style="width:9%">节点类型</th>
              <!-- 仅在 resolver 模式下显示“转发器 IP”列 -->
              <th v-if="currentNodeType === 'resolver'" style="width:15%">
                对应转发器IP
              </th>
              <th style="width:9%">访问方式</th>
              <th style="width:18%">城市 / 国家</th>
              <th style="width:10%">国家 / 地区</th>
              <th style="width:7%">ASN</th>
              <th style="width:11%">运营商</th>
              <th style="width:10%">IP 前缀</th>
              <th style="width:4%">纬度</th>
              <th style="width:4%">经度</th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="(item, idx) in tableData" :key="item.ip + '_' + idx">
              <td class="col-ip">{{ item.ip || '-' }}</td>
              <td>
                <span class="tag">{{ displayTypeLabel(item.label) }}</span>
              </td>
              <!-- resolver 时显示，forwarder 时整列隐藏 -->
              <td v-if="currentNodeType === 'resolver'">
                {{ item.forwarderIp || '-' }}
              </td>
              <td>
                <span class="tag">{{ displayaccessMethod(item.accessMethod) }}</span>
              </td>
              <td>{{ item.location || '-' }}</td>
              <td>
                <span class="country-badge">{{ item.countryDisplay || '-' }}</span>
              </td>
              <td>{{ item.asn ?? '-' }}</td>
              <td>{{ item.asnOrg || '-' }}</td>
              <td>{{ item.cidr || '-' }}</td>
              <td>{{ typeof item.lat === 'number' ? item.lat.toFixed(4) : '-' }}</td>
              <td>{{ typeof item.lng === 'number' ? item.lng.toFixed(4) : '-' }}</td>
            </tr>
            <tr v-if="tableData.length === 0 && !tableLoading">
              <td :colspan="emptyColspan" style="text-align: center; padding: 20px; color: #999;">
                暂无数据
              </td>
            </tr>
          </tbody>
        </table>

        <!-- 分页 -->
        <div class="table-pagination">
          <span class="pagination-summary">
            第 {{ currentPage }} / {{ totalPages }} 页
          </span>
          <button
            class="page-btn"
            :class="{ disabled: currentPage === 1 }"
            @click="goPage(currentPage - 1)"
          >
            «
          </button>
          <span class="current-page-display">{{ currentPage }}</span>
          <button
            class="page-btn"
            :class="{ disabled: currentPage === totalPages }"
            @click="goPage(currentPage + 1)"
          >
            »
          </button>
        </div>
      </div>
    </section>
  </div>
</template>

<script setup lang="ts">
import {
  ref,
  reactive,
  onMounted,
  onBeforeUnmount,
  computed,
  watch
} from 'vue'
import * as L from 'leaflet'
import type { Map as LeafletMap, GeoJSON, LayerGroup } from 'leaflet'
import 'leaflet/dist/leaflet.css'

import {
  fetchCountryCounts,
  fetchMapCoordinates,
  fetchNodeTableData,
  type IpGeoTableItem,
  type CountryCountMap
} from '../services/ipGeoMap'

// ===================== 状态定义 =====================

const mapContainer = ref<HTMLDivElement | null>(null)
const mapRef = ref<LeafletMap | null>(null)
const ipLayerRef = ref<LayerGroup | null>(null)
const worldLayerRef = ref<GeoJSON | null>(null)
const mapLoading = ref(false)

// 已合并处理的国家数据
const mergedCountryCounts = reactive<Record<string, number>>({})
const currentMaxCount = ref(0)

const tableLoading = ref(false)
const currentNodeType = ref<'forwarder' | 'resolver'>('forwarder')
const tableData = ref<IpGeoTableItem[]>([])
const totalCount = ref(0)
const pageSize = ref(50)
const currentPage = ref(1)

// “暂无数据”行的 colspan，随列数动态变化
const emptyColspan = computed(() =>
  currentNodeType.value === 'resolver' ? 11 : 10
)

// ===================== 地图着色逻辑 =====================

function normalizeCountryName(name: string): string {
  const n = (name || '').trim().toLowerCase()
  if (!n) return ''

  // 中国 & 台湾
  if (n === 'china' || n === 'prc' || n.includes("people's republic of china")) return 'China'
  if (n === 'taiwan' || n === 'roc' || n.includes('republic of china')) return 'Taiwan'

  // 美国
  if (n === 'united states' || n === 'usa' || n === 'united states of america') return 'United States'

  // 俄罗斯
  if (n === 'russia' || n === 'russian federation') return 'Russia'

  // 英国
  if (n === 'united kingdom' || n === 'uk' || n === 'great britain') return 'United Kingdom'

  // 韩国
  if (n.includes('korea, republic') || n === 'south korea') return 'South Korea'

  // 默认: 将单词首字母大写以增加匹配概率
  return n.charAt(0).toUpperCase() + n.slice(1)
}

function processCountryCounts(rawCounts: CountryCountMap) {
  // 清空旧数据
  for (const k in mergedCountryCounts) delete mergedCountryCounts[k]

  const tempCounts: Record<string, number> = {}

  Object.entries(rawCounts).forEach(([key, count]) => {
    let normKey = normalizeCountryName(key)
    if (!normKey) normKey = key
    tempCounts[normKey] = (tempCounts[normKey] || 0) + count
  })

  // 合并 China 和 Taiwan
  const countCN = tempCounts['China'] || 0
  const countTW = tempCounts['Taiwan'] || 0
  const totalCN = countCN + countTW

  if (totalCN > 0) {
    tempCounts['China'] = totalCN
    tempCounts['Taiwan'] = totalCN
  } else {
    delete tempCounts['China']
    delete tempCounts['Taiwan']
  }

  let max = 0
  Object.entries(tempCounts).forEach(([k, v]) => {
    mergedCountryCounts[k] = v
    if (v > max) max = v
  })
  currentMaxCount.value = max
}

function updateChoropleth() {
  const worldLayer = worldLayerRef.value
  if (!worldLayer) return

  const max = currentMaxCount.value

  worldLayer.eachLayer((layer) => {
    const feature = (layer as any).feature
    const rawName = feature?.properties?.name as string
    const name = normalizeCountryName(rawName)

    const count = mergedCountryCounts[name] || mergedCountryCounts[rawName] || 0

    let fillColor = '#f5f5ff'

    if (count > 0 && max > 0) {
      if (count > max * 0.7) {
        fillColor = '#3d5afe'
      } else if (count > max * 0.4) {
        fillColor = '#6c8bff'
      } else if (count > max * 0.1) {
        fillColor = '#9fb3ff'
      } else {
        fillColor = '#c9d4ff'
      }
    }

    ;(layer as any).setStyle({
      fillColor,
      fillOpacity: 0.7,
      color: '#d0d6f4',
      weight: 0.5
    })
  })
}

const legendLabels = computed(() => {
  const max = currentMaxCount.value
  if (!max || max <= 0) return ['-', '-', '-', '-']

  const p10 = Math.floor(max * 0.1)
  const p40 = Math.floor(max * 0.4)
  const p70 = Math.floor(max * 0.7)

  return [`1 - ${p10}`, `${p10 + 1} - ${p40}`, `${p40 + 1} - ${p70}`, `${p70 + 1} - ${max}`]
})

// ===================== 地图初始化 =====================

async function initMap() {
  if (!mapContainer.value) return

  const map = L.map(mapContainer.value, {
    worldCopyJump: true,
    zoomControl: true,
    minZoom: 1.5,
    preferCanvas: true
  }).setView([25, 10], 2)

  const TILE_URL =
    import.meta.env.VITE_TILE_URL || 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
  L.tileLayer(TILE_URL, { maxZoom: 19, attribution: '© OpenStreetMap' }).addTo(map)

  const ipLayer = L.layerGroup().addTo(map)
  mapRef.value = map
  ipLayerRef.value = ipLayer

  try {
    const res = await fetch(
      'https://raw.githubusercontent.com/johan/world.geo.json/master/countries.geo.json'
    )
    const data = await res.json()
    const worldLayer = L.geoJSON(data, {
      style: { fillColor: '#f5f5ff', fillOpacity: 0.7, color: '#d0d6f4', weight: 0.5 },
      interactive: false
    }).addTo(map)

    worldLayerRef.value = worldLayer
    worldLayer.bringToBack()

    if (currentMaxCount.value > 0) {
      updateChoropleth()
    }
  } catch (e) {
    console.error('加载世界地图数据失败', e)
  }
}

async function loadMapData() {
  mapLoading.value = true
  try {
    const [counts, coords] = await Promise.all([fetchCountryCounts(), fetchMapCoordinates()])

    processCountryCounts(counts)
    updateChoropleth()

    if (ipLayerRef.value) {
      ipLayerRef.value.clearLayers()
      coords.forEach((pt) => {
        L.circleMarker([pt.latitude, pt.longitude], {
          radius: 2,
          weight: 0,
          fillOpacity: 0.6,
          fillColor: '#6c5ce7'
        }).addTo(ipLayerRef.value!)
      })
    }
  } catch (e) {
    console.error('加载地图相关数据失败:', e)
  } finally {
    mapLoading.value = false
  }
}

// ===================== 表格逻辑 =====================

const totalPages = computed(() =>
  Math.max(1, Math.ceil(totalCount.value / pageSize.value))
)

function switchNodeType(type: 'forwarder' | 'resolver') {
  if (currentNodeType.value === type) return
  currentNodeType.value = type
  currentPage.value = 1
  loadTableData()
}

function goPage(p: number) {
  if (p < 1 || p > totalPages.value) return
  currentPage.value = p
  loadTableData()
}

watch(pageSize, () => {
  currentPage.value = 1
  loadTableData()
})

async function loadTableData() {
  tableLoading.value = true
  try {
    const index = (currentPage.value - 1) * pageSize.value
    const count = pageSize.value
    const res = await fetchNodeTableData(currentNodeType.value, index, count)
    tableData.value = res.items
    totalCount.value = res.total
  } catch (e) {
    console.error('表格数据加载失败', e)
  } finally {
    tableLoading.value = false
  }
}

function displayTypeLabel(label?: string): string {
  const t = (label || '').toLowerCase()
  if (t === 'forwarder') return 'DNS 转发器'
  if (t === 'resolver') return 'DNS 解析器'
  return label || '-'
}

function displayaccessMethod(accessMethod?: string): string {
  const x = (accessMethod || '').toLowerCase().trim()
  if (x === 'public_accessible') return '支持公开访问'
  if (x === 'via_forwarder') return '只能转发访问'
  return accessMethod || '-'
}

onMounted(async () => {
  await initMap()
  loadMapData()
  loadTableData()
})

onBeforeUnmount(() => {
  if (mapRef.value) {
    mapRef.value.remove()
    mapRef.value = null
  }
})
</script>

<style scoped>
.page-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 16px;
  margin-left: 0px;
  gap: 8px;
}
.page-title-block {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.page-title {
  font-size: 18px;
  font-weight: 600;
  color: #242a3b;
  display: flex;
  align-items: center;
  gap: 8px;
}
.page-subtitle {
  font-size: 13px;
  color: #8c92a4;
}
.page-badge {
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 12px;
  background: rgba(108, 92, 231, 0.08);
  color: #6c5ce7;
}

.card {
  background: #ffffff;
  border-radius: 16px;
  box-shadow: 0 10px 30px rgba(15, 35, 95, 0.08);
  padding: 16px 18px 18px;
  display: flex;
  flex-direction: column;
  min-height: 520px;
}
.map-card {
  margin-left: 0px;
}
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
  gap: 8px;
}
.card-title {
  font-size: 15px;
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 8px;
}
.card-title-icon {
  width: 26px;
  height: 26px;
  border-radius: 999px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: radial-gradient(
    circle at 30% 0,
    #ffeaa7 0,
    #fd79a8 40%,
    #6c5ce7 100%
  );
  color: #fff;
  font-size: 14px;
}
.card-subtitle {
  font-size: 12px;
  color: #9aa0b5;
  margin-top: 2px;
}

.map-container {
  height: 360px;
  border-radius: 12px;
  overflow: hidden;
  margin-top: 8px;
  border: 1px solid #e4e7f3;
  position: relative;
  z-index: 1; /* 整个地图区域层级降低，避免遮挡顶栏 */
}
.map-container :deep(.leaflet-container) {
  width: 100%;
  height: 100%;
}
/* 进一步降低 Leaflet 内部 pane 的 z-index，保证外层顶栏永远在上面 */
.map-container :deep(.leaflet-pane),
.map-container :deep(.leaflet-top),
.map-container :deep(.leaflet-bottom) {
  z-index: 1 !important;
}

.map-loading-overlay {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(255, 255, 255, 0.7);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 2000;
  color: #6c5ce7;
  font-weight: 600;
}

.map-legend {
  position: absolute;
  bottom: 10px;
  right: 10px;
  background: rgba(255, 255, 255, 0.9);
  border-radius: 8px;
  padding: 6px 10px;
  font-size: 11px;
  box-shadow: 0 4px 10px rgba(15, 35, 95, 0.2);
  z-index: 1000;
}
.map-legend-title {
  font-weight: 600;
  margin-bottom: 4px;
  color: #4b4f63;
}
.map-legend-item {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-top: 2px;
}
.map-legend-color {
  width: 12px;
  height: 8px;
  border-radius: 999px;
}

.table-section-title {
  margin-top: 14px;
  font-size: 14px;
  font-weight: 600;
  color: #2f3640;
}
.table-section-subtitle {
  margin-top: 4px;
  font-size: 12px;
  color: #8c92a4;
}
.table-toolbar {
  margin-top: 10px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.toolbar-left {
  display: flex;
  align-items: center;
}
.toolbar-right {
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 12px;
  color: #757b8f;
}

.capsule-switch {
  display: flex;
  background: #f2f4ff;
  border-radius: 999px;
  padding: 3px;
  border: 1px solid #e4e7f3;
}
.capsule-item {
  padding: 5px 16px;
  font-size: 12px;
  border-radius: 999px;
  cursor: pointer;
  color: #636e72;
  transition: all 0.2s ease;
  user-select: none;
}
.capsule-item.active {
  background: #fff;
  color: #6c5ce7;
  font-weight: 600;
  box-shadow: 0 2px 6px rgba(108, 92, 231, 0.15);
}

.page-size-select {
  padding: 4px 8px;
  border-radius: 999px;
  border: 1px solid #dde2f4;
  font-size: 12px;
  background: #fff;
  outline: none;
}
.table-wrapper {
  margin-top: 8px;
  border-radius: 12px;
  border: 1px solid #e4e7f3;
  overflow: hidden;
  background: #fafbff;
  position: relative;
  min-height: 200px;
}
.table-loading-overlay {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(255, 255, 255, 0.6);
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #6c5ce7;
}

table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12px;
}
thead {
  background: #f2f4ff;
}
th,
td {
  padding: 8px 10px;
  text-align: left;
  border-bottom: 1px solid #eceff7;
  white-space: nowrap;
}
th {
  font-weight: 600;
  color: #5b627a;
  user-select: none;
}
tbody tr:nth-child(even) {
  background: #f9f9ff;
}
tbody tr:hover {
  background: #eef1ff;
}
.col-ip {
  font-family: monospace;
  font-size: 11px;
}
.tag {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 11px;
  background: rgba(108, 92, 231, 0.08);
  color: #6c5ce7;
}

.country-badge {
  padding: 2px 8px;
  border-radius: 999px;
  background: #fff;
  border: 1px solid #dde2f4;
  font-size: 11px;
  color: #636e72;
}

.table-pagination {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 6px;
  padding: 8px 10px;
  font-size: 12px;
  color: #757b8f;
}
.page-btn {
  min-width: 24px;
  height: 24px;
  padding: 0 6px;
  border-radius: 999px;
  border: 1px solid #dde2f4;
  background: #fff;
  color: #555b73;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.page-btn:hover:not(.disabled) {
  border-color: #6c5ce7;
  color: #6c5ce7;
}
.page-btn.disabled {
  opacity: 0.4;
  cursor: default;
}
.current-page-display {
  font-weight: 600;
  color: #242a3b;
  padding: 0 8px;
}
.pagination-summary {
  margin-right: 8px;
}

@media (max-width: 768px) {
  .card {
    padding: 12px;
  }
  .map-container {
    height: 280px;
  }
}
</style>